﻿package birdry.box2d
{


	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Collision.Shapes.b2PolygonShape;
	import Box2D.Collision.Shapes.b2Shape;
	import Box2D.Collision.b2AABB;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.Joints.b2DistanceJoint;
	import Box2D.Dynamics.Joints.b2DistanceJointDef;
	import Box2D.Dynamics.Joints.b2FrictionJoint;
	import Box2D.Dynamics.Joints.b2FrictionJointDef;
	import Box2D.Dynamics.Joints.b2GearJoint;
	import Box2D.Dynamics.Joints.b2GearJointDef;
	import Box2D.Dynamics.Joints.b2Joint;
	import Box2D.Dynamics.Joints.b2LineJoint;
	import Box2D.Dynamics.Joints.b2LineJointDef;
	import Box2D.Dynamics.Joints.b2PrismaticJoint;
	import Box2D.Dynamics.Joints.b2PrismaticJointDef;
	import Box2D.Dynamics.Joints.b2PulleyJoint;
	import Box2D.Dynamics.Joints.b2PulleyJointDef;
	import Box2D.Dynamics.Joints.b2RevoluteJoint;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
	import Box2D.Dynamics.Joints.b2WeldJoint;
	import Box2D.Dynamics.Joints.b2WeldJointDef;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2DebugDraw;
	import Box2D.Dynamics.b2Fixture;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Dynamics.b2World;

	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Sprite;
	import flash.display.Stage;





	/**
	* Box2D Util
	* 
	* 2010. 04. 29  - 최초 작성
	* 
	* 2010. 05. 03  - createDistanceJoint 추가
	*                                                                            - createRevoluteJoint 추가
	*                                                                            - createPulleyJoint 추가
	*                                                                            - createBodyWithPolygon -> createBodyWithBox 변경
	*                                                                            - createBodyWithPolygon 추가
	* 
	* 2010. 05. 04  - createPrismaticJoint 추가
	*                                                                            - createGearJoint 추가
	*                                                                            - createRevoluteJoint 수정
	*                                                                            - setDebugDraw 수정
	* 
	* 2010. 05. 06  - createFrictionJoint 추가
	*                                                                            - createLineJoint 추가
	*                                                                            - createWeldJoint 추가
	* 
	* @author Mozi
	* 
	*/         
	public class B2Util
	{

		//--------------------------------------------------------------------------
		//
		//           Constructor
		//
		//--------------------------------------------------------------------------

		/**
		* Box2D Util Constructor
		*/                      
		public function B2Util()		{
		}





		//--------------------------------------------------------------------------
		//
		//           Public Static Function
		//
		//--------------------------------------------------------------------------

		/**
		* Get Body At Mouse
		* 
		* @param world                          :: World
		* @param mouseX         :: Mouse X Point
		* @param mouseY         :: Mouse Y Point
		* @return                                                 :: b2Body At Mouse Point
		* 
		*/                      
		public static function getBodyAtMouse( world:b2World, mouseX:Number, mouseY:Number ) : b2Body		{
			var physMouseX:Number = mouseX/30;
			var physMouseY:Number = mouseY/30;

			var mousePVec:b2Vec2    = new b2Vec2( physMouseX, physMouseY );

			var body:b2Body = null;

			var aabb:b2AABB             = new b2AABB();
			aabb.lowerBound.Set( physMouseX - 0.001, physMouseY - 0.001 );
			aabb.upperBound.Set( physMouseX + 0.001, physMouseY + 0.001 );

			function queryAABBCallback( fixture:b2Fixture ) : void
			{
				 var shape:b2Shape           = fixture.GetShape();
				 
				 if( fixture.GetBody().GetType()      != b2Body.b2_staticBody )
				 {
							  var inside:Boolean           = shape.TestPoint( fixture.GetBody().GetTransform(), mousePVec );
							  
							  if( inside )
							  {
											body      = fixture.GetBody();
							  }
				 }
			}

			world.QueryAABB( queryAABBCallback, aabb ); 

			return body;
		}



		/**
		* Set Debug Draw
		* 
		* @param container        :: DebugDraw on Container
		* @param world                          :: World
		* 
		*/                                                
		public static function setDebugDraw( container:DisplayObjectContainer, world:b2World, flags:uint=0xFFFFFF ) : b2DebugDraw
		{
			var spr:Sprite       = new Sprite();
			container.addChild( spr );

			var debugDraw:b2DebugDraw       = new b2DebugDraw();
			debugDraw.SetSprite( spr );
			debugDraw.SetDrawScale( 30 );
			debugDraw.SetFillAlpha( 0.5 );

			if( flags == 0xFFFFFF )
			{
				 debugDraw.SetFlags( b2DebugDraw.e_jointBit | b2DebugDraw.e_shapeBit );
			}
			else
			{
				 debugDraw.SetFlags( flags );
			}

			debugDraw.SetLineThickness( 1 );

			world.SetDebugDraw( debugDraw );

			return debugDraw;
		}



		/**
		* Create Box
		* 
		* @param world                                        :: World
		* @param width                                        :: Width( Pixel )
		* @param height                                       :: Height( Pixel )
		* @param density                        :: Polygon Density
		* @param friction                        :: Polygon Friction
		* @param restitution      :: Polygon Restitution
		* @param x                                                            :: Body X Position
		* @param y                                                            :: Body Y Position
		* @param angle                                        :: Body Angle
		* @param bodyType       :: Body Type
		* @param container                     :: Container for userData, Container.addChild( userData );
		* @param userData                      :: UserData( DiplayObject( Graphics Object ) )
		* @return                                                              :: b2Body
		* 
		*/                                                
		public static function createBodyWithBox( world:b2World, width:Number, height:Number, density:Number=0, friction:Number=0, restitution:Number=0, x:Number=0, y:Number=0, angle:Number=0, bodyType:uint=2, container:DisplayObjectContainer=null, userData:DisplayObject=null ) : b2Body
		{
			var polygonShape:b2PolygonShape             = new b2PolygonShape();
			polygonShape.SetAsBox( ( width/2 )/30, ( height/2 )/30 );

			var fixtureDef:b2FixtureDef = B2Util.createFixtureDef( density, friction, restitution, polygonShape );

			var body:b2Body = B2Util.createBodyOfWorld( world, fixtureDef, x, y, bodyType, container, userData, width, height, angle );

			return body;
		}



		/**
		* Create Polygon
		* 
		* @param world                                        :: World
		* @param vertices                        :: Vertices
		* @param density                        :: Density
		* @param friction                        :: Friction
		* @param restitution      :: Restitution
		* @param x                                                            :: X
		* @param y                                                            :: Y
		* @param angle                                        :: Angle
		* @param bodyType       :: Body Type
		* @param container                     :: Container for userData, Container.addChild( userData );
		* @param userData                      :: UserData( DiplayObject( Graphics Object ) )
		* @param width                                        :: Width( Pixel )
		* @param height                                       :: Height( Pixel )
		* @return 
		* 
		*/                      
		public static function createBodyWithPolygon( world:b2World, vertices:Array, density:Number=0, friction:Number=0, restitution:Number=0, x:Number=0, y:Number=0, angle:Number=0, bodyType:uint=2, container:DisplayObjectContainer=null, userData:DisplayObject=null, width:Number=0, height:Number=0 ) : b2Body
		{
			var polygonShape:b2PolygonShape             = new b2PolygonShape();
			polygonShape.SetAsArray( vertices, vertices.length );

			var fixtureDef:b2FixtureDef            = B2Util.createFixtureDef( density, friction, restitution, polygonShape );

			var body:b2Body = B2Util.createBodyOfWorld( world, fixtureDef, x, y, bodyType, container, userData, width, height, angle );

			return body;
		}



		/**
		* Create Circle
		* 
		* @param world                                        :: World
		* @param radius                                       :: Radius( Pixel )
		* @param density                        :: Circle Density
		* @param friction                        :: Circle Friction
		* @param restitution      :: Circle Restitution
		* @param x                                                            :: Body X Position
		* @param y                                                            :: Body Y Position
		* @param bodyType       :: Body Type
		* @param container                     :: Container for userData, Container.addChild( userData );
		* @param userData                      :: UserData( DisplayObject( Graphics Object ) )
		* @return                                                              :: b2Body
		* 
		*/                      
		public static function createBodyWithCircle( world:b2World, radius:Number, density:Number=0, friction:Number=0, restitution:Number=0, x:Number=0, y:Number=0, bodyType:uint=2, container:DisplayObjectContainer=null, userData:DisplayObject=null ) : b2Body
		{
			var circleShape:b2CircleShape        = new b2CircleShape( radius/30 );

			var fixtureDef:b2FixtureDef            = B2Util.createFixtureDef( density, friction, restitution, circleShape );

			var body:b2Body = B2Util.createBodyOfWorld( world, fixtureDef, x, y, bodyType, container, userData, radius*2, radius*2, 0 );

			return body;
		}



		/**
		* Create FixtureDef
		* 
		* @param density                        :: Density
		* @param friction                        :: Friction
		* @param restitution      :: Restitution
		* @param shape                                       :: Shape
		* @return                                                              :: b2FixtureDef
		* 
		*/                      
		public static function createFixtureDef( density:Number=0, friction:Number=0, restitution:Number=0, shape:b2Shape=null ) : b2FixtureDef
		{
			var fixtureDef:b2FixtureDef            = new b2FixtureDef();
			fixtureDef.density                                       = density;
			fixtureDef.friction                                       = friction;
			fixtureDef.restitution        = restitution;
			
			if( shape )			{
				 fixtureDef.shape               = shape;
			}
			
			return fixtureDef;
		}



		/**
		* Create Body Of World
		* 
		* @param world                                        :: World
		* @param fixtureDef       :: FixtureDef
		* @param x                                                            :: Body X Position
		* @param y                                                            :: Body Y Position
		* @param bodyType       :: Body Type
		* @param container                     :: Container for userData, Container.addChild( userData );
		* @param userData                      :: UserData( DisplayObject( Graphics Object ) )
		* @param width                                        :: userData Width( Pixel )
		* @param height                                       :: userData Height( Pixel )
		* @param angle                                        :: Body Angle
		* @return                                                              :: b2Body
		* 
		*/                      
		private static function createBodyOfWorld( world:b2World, fixtureDef:b2FixtureDef=null, x:Number=0, y:Number=0, bodyType:uint=2, container:DisplayObjectContainer=null, userData:DisplayObject=null, width:Number=0, height:Number=0, angle:Number=0 ) : b2Body
		{
			var bodyDef:b2BodyDef    = new b2BodyDef();
			bodyDef.position.Set( x/30, y/30 );
			bodyDef.angle     = angle;
			bodyDef.type      = bodyType;
			
			if( userData )			{
				bodyDef.userData             = userData;
				bodyDef.userData.width   = width;
				bodyDef.userData.height  = height;

				if( container )				{
					container.addChild( bodyDef.userData as DisplayObject );
				}
				else				{
					throw new ArgumentError( "userData 를 적용하는 경우 userData 를 container 를 파라메터로 넘겨줘야 합니다" ); 
				}
			}
			
			var body:b2Body = world.CreateBody( bodyDef );
			
			if( fixtureDef )			{
						 body.CreateFixture( fixtureDef );
			}
			
			return body;
		}



		/**
		* Create Distance Joint
		* 
		* @param world                                                                               :: World
		* @param bodyA                                                                              :: BodyA
		* @param bodyB                                                                               :: BodyB
		* @param dampingRatio                           :: Damping Ratio
		* @param frequencyHz                              :: Frequency Hz
		* @param collideConnected                      :: Collide Connected
		* @param anchorA                                                              :: AnchorA
		* @param anchorB                                                              :: AnchorB
		* @return 
		* 
		*/                                   
		public static function createDistanceJoint( world:b2World, bodyA:b2Body, bodyB:b2Body, dampingRatio:Number=0, frequencyHz:Number=0, collideConnected:Boolean=true, anchorA:b2Vec2=null, anchorB:b2Vec2=null ) : b2DistanceJoint
		{
			var distanceJointDef:b2DistanceJointDef      = new b2DistanceJointDef();
			distanceJointDef.bodyA    = bodyA;
			distanceJointDef.bodyB    = bodyB;
			distanceJointDef.collideConnected = collideConnected;
			distanceJointDef.dampingRatio      = dampingRatio;
			distanceJointDef.frequencyHz        = frequencyHz;
			
			switch( anchorA )
			{
				 case null :
							  anchorA = bodyA.GetPosition();
							  break;
			}
			
			switch( anchorB )
			{
				 case null :
							  anchorB = bodyB.GetPosition();
							  break;
			}
			
			distanceJointDef.Initialize( bodyA, bodyB, anchorA, anchorB );
			
			return world.CreateJoint( distanceJointDef ) as b2DistanceJoint;
		}



		/**
		* Create Revolute Joint
		* 
		* @param world                                                                               :: World
		* @param bodyA                                                                              :: BodyA
		* @param bodyB                                                                               :: BodyB
		* @param anchor                                                                :: Anchor
		* @param collideConnected                      :: CollideConnected
		* @return 
		* 
		*/                      
		public static function createRevoluteJoint( world:b2World, bodyA:b2Body, bodyB:b2Body, anchor:b2Vec2=null, motorSpeed:Number=NaN, maxMotorTorque:Number=NaN, collideConnected:Boolean=false ) : b2RevoluteJoint
		{
			var revoluteJointDef:b2RevoluteJointDef      = new b2RevoluteJointDef();
			revoluteJointDef.bodyA    = bodyA;
			revoluteJointDef.bodyB     = bodyB;
			revoluteJointDef.collideConnected = collideConnected;

			switch( anchor )
			{
				 case null :
							  anchor   = bodyA.GetPosition();
							  break;
			}

			if( !isNaN( motorSpeed ) )
			{
				 revoluteJointDef.motorSpeed         = motorSpeed;
				 revoluteJointDef.enableMotor        = true;
			}

			if( !isNaN( maxMotorTorque ) )
			{
				 revoluteJointDef.maxMotorTorque              = maxMotorTorque;
			}

			revoluteJointDef.Initialize( bodyA, bodyB, anchor );

			return world.CreateJoint( revoluteJointDef ) as b2RevoluteJoint;
		}



		/**
		* Create Pulley Joint
		* 
		* @param world                                                                               :: World
		* @param bodyA                                                                              :: BodyA
		* @param bodyB                                                                               :: BodyB
		* @param groundAnchorA                        :: Ground AnchorA
		* @param groundAnchorB                        :: Ground AnchorB
		* @param maxLengthA                             :: Max LengthA
		* @param maxLengthB                             :: Max LengthB
		* @param ratio                                                                    :: Ratio
		* @param collideConnected                      :: Collide Connected
		* @return 
		* 
		*/                      
		public static function createPulleyJoint( world:b2World, bodyA:b2Body, bodyB:b2Body, groundAnchorA:b2Vec2, groundAnchorB:b2Vec2, maxLengthA:Number, maxLengthB:Number, ratio:Number=1, collideConnected:Boolean=true ) : b2PulleyJoint
		{
			var pulleyJointDef:b2PulleyJointDef             = new b2PulleyJointDef();
			pulleyJointDef.bodyA        = bodyA;
			pulleyJointDef.bodyB                     = bodyB;
			pulleyJointDef.collideConnected     = collideConnected;
			pulleyJointDef.maxLengthA            = maxLengthA;
			pulleyJointDef.maxLengthB            = maxLengthB;
			pulleyJointDef.ratio           = ratio;

			pulleyJointDef.groundAnchorA      = groundAnchorA;
			pulleyJointDef.groundAnchorB       = groundAnchorB;

			pulleyJointDef.Initialize( bodyA, bodyB, groundAnchorA, groundAnchorB, bodyA.GetPosition(), bodyB.GetPosition(), ratio );

			return world.CreateJoint( pulleyJointDef ) as b2PulleyJoint;
		}



		/**
		* Create Prismatic Joint
		* 
		* @param world                                                                               :: World
		* @param body                                                                   :: Body
		* @param axis                                                                                  :: b2Vec2
		* @param lowerTranslation                       :: Lower Translation
		* @param upperTranslation                       :: Upper Translation
		* @param enableLimit                                            :: EnableLimit
		* @param collideConnected                      :: Collide Connected
		* @return 
		* 
		*/                      
		public static function createPrismaticJoint( world:b2World, bodyA:b2Body, bodyB:b2Body, axis:b2Vec2, lowerTranslation:Number, upperTranslation:Number, motorSpeed:Number=NaN, maxMotorForce:Number=NaN, enableLimit:Boolean=true, collideConnected:Boolean=true ) : b2PrismaticJoint
		{
			var prismaticJointDef:b2PrismaticJointDef   = new b2PrismaticJointDef();
			prismaticJointDef.lowerTranslation = lowerTranslation;
			prismaticJointDef.upperTranslation             = upperTranslation;
			prismaticJointDef.enableLimit                                   = enableLimit;
			prismaticJointDef.collideConnected             = collideConnected;

			if( !isNaN( motorSpeed ) )
			{
				prismaticJointDef.motorSpeed                                  = motorSpeed;
				prismaticJointDef.enableMotor                                 = true;
			}

			if( !isNaN( maxMotorForce ) )
			{
				 prismaticJointDef.maxMotorForce               = maxMotorForce;
			}

			prismaticJointDef.Initialize( bodyA, bodyB, bodyB.GetPosition(), axis );

			return world.CreateJoint( prismaticJointDef ) as b2PrismaticJoint;
		}



		/**
		* Create Gear Joint
		* 
		* @param world             :: World
		* @param bodyA            :: BodyA
		* @param bodyB            :: BodyB
		* @param jointA             :: JointA
		* @param jointB             :: JointB
		* @param ratio  :: Ratio
		* @return 
		* 
		*/                      
		public static function createGearJoint( world:b2World, bodyA:b2Body, bodyB:b2Body, jointA:b2Joint, jointB:b2Joint, ratio:Number=1, collideConnected:Boolean=true ) : b2GearJoint
		{
			var gearJointDef:b2GearJointDef    = new b2GearJointDef();
			gearJointDef.bodyA          = bodyA;
			gearJointDef.bodyB          = bodyB;
			gearJointDef.joint1           = jointA;
			gearJointDef.joint2           = jointB;
			gearJointDef.ratio                          = ratio;
			gearJointDef.collideConnected       = collideConnected;

			return world.CreateJoint( gearJointDef ) as b2GearJoint;
		}



		/**
		* Create Friction Joint
		* 
		* @param world                                                                               :: World
		* @param bodyA                                                                              :: BodyA
		* @param bodyB                                                                               :: BodyB
		* @param maxTorque                                            :: Max Torque
		* @param maxForce                                               :: Max Force
		* @param collideConnected                      :: CollideConnected
		* @return 
		* 
		*/                      
		public static function createFrictionJoint( world:b2World, bodyA:b2Body, bodyB:b2Body, maxTorque:Number=NaN, maxForce:Number=NaN, collideConnected:Boolean=true ) : b2FrictionJoint
		{
			var frictionJointDef:b2FrictionJointDef         = new b2FrictionJointDef();
			frictionJointDef.bodyA      = bodyA;
			frictionJointDef.bodyB      = bodyB;

			if( !isNaN( maxTorque ) )			{
				frictionJointDef.maxTorque            = maxTorque;
			}

			if( !isNaN( maxForce ) )			{
				frictionJointDef.maxForce = maxForce;
			}

			frictionJointDef.collideConnected   = collideConnected;

			return world.CreateJoint( frictionJointDef ) as b2FrictionJoint;
		}



		/**
		* Create Line Joint
		* 
		* @param world                                                                               :: World
		* @param bodyA                                                                              :: BodyA
		* @param bodyB                                                                               :: BodyB
		* @param motorSpeed                              :: Motor Speed
		* @param maxMotorForce                        :: Max Motor Force
		* @param collideConnected                      :: Collide Connteced
		* @return 
		* 
		*/                      
		public static function createLineJoint( world:b2World, bodyA:b2Body, bodyB:b2Body, motorSpeed:Number=NaN, maxMotorForce:Number=NaN, collideConnected:Boolean=true ) : b2LineJoint
		{
			var lineJointDef:b2LineJointDef      = new b2LineJointDef();
			lineJointDef.bodyA           = bodyA;
			lineJointDef.bodyB            = bodyB;

			if( !isNaN( motorSpeed ) )
			{
				 lineJointDef.motorSpeed                             = motorSpeed;
				 lineJointDef.enableMotor                            = true;
			}

			if( !isNaN( maxMotorForce ) )
			{
				 lineJointDef.maxMotorForce                       = maxMotorForce;
			}

			lineJointDef.collideConnected        = collideConnected;

			return world.CreateJoint( lineJointDef ) as b2LineJoint;
		}



		/**
		* Create Weld Joint
		* 
		* @param world                                                                               :: World
		* @param bodyA                                                                              :: BodyA
		* @param bodyB                                                                               :: BodyB
		* @param collideConnected                      :: CollideConnected
		* @return 
		* 
		*/                      
		public static function createWeldJoint( world:b2World, bodyA:b2Body, bodyB:b2Body, collideConnected:Boolean=false ) : b2WeldJoint
		{
			var weldJointDef:b2WeldJointDef   = new b2WeldJointDef();
			weldJointDef.bodyA         = bodyA;
			weldJointDef.bodyB          = bodyB;
			weldJointDef.collideConnected      = collideConnected;

			return world.CreateJoint( weldJointDef ) as b2WeldJoint;
		}



		/**
		* Create Static Walls
		* <br/>
		* wall size, use container width, height
		* <br/>
		* if container is stage, use stageWidth, stageHeight
		* 
		* @param target                          :: Walls on Container
		* @param world                          :: World
		* @param friction           :: Walls Friction
		* 
		*/                      
		public static function createStaticWalls( container:DisplayObjectContainer, world:b2World, friction:Number=0 ) : void
		{
			var width:Number;
			var height:Number;

			var stage:Stage   = container as Stage;

			if( stage )			{
				 width     = stage.stageWidth;
				 height    = stage.stageHeight;
			}
			
			else			{
				 width     = container.width;
				 height    = container.height;
			}
			
			var arrX:Array      =           	[ width/2, width/2, 0, width ];
			var arrY:Array      = 			[ 0, height, height/2, height/2 ];
			var arrWidth:Array            = 	[ width, width, 1, 1 ];
			var arrHeight:Array           = 	[ 1, 1, height, height ];

			var body:b2Body;
			var fixtureDef:b2FixtureDef;

			for( var i:int; i<4; ++i )			{
				 B2Util.createBodyWithBox( world, arrWidth[ i ], arrHeight[ i ], 0, 0, 0, arrX[ i ], arrY[ i ], 0, b2Body.b2_staticBody );
			}
		}
	}
}

